/**
 * GrfPack.hpp - This file is part of GRFPack
 * Copyright (C) 2012 FatalEror / Dean Bahari
 *
 * GRFPack is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GRFPack is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License in main folder for more details.
 */

#ifndef _GRFPACK_HPP_
#define _GRFPACK_HPP_

#ifdef __unix__
#include <stdint.h>
#else
#include <cstdint>
#endif

#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <zlib.h>

struct GrfHeader;
class GrfItem;
class GrfFile;

#define MAGIC_KEY "Master of Magic\0"

#pragma pack(push, 2)

struct GrfHeader {
  uint8_t signature[16];
  uint8_t allowEncryption[14];
  uint32_t fileTableOffset;
  uint32_t number1;
  uint32_t number2;
  uint32_t version;
};

#pragma pack(pop)

class GrfItem {
  GrfFile *m_parent;
  uint8_t *m_fileData;
  uint32_t m_offset; // This is not the offset of the file data in GRF! Use getOffset instead.
public:
  GrfItem();
  ~GrfItem();

  // Return full path of the file
  std::string getPath();

  // Return name of the file
  std::string getName();

  // Return compressed data size
  uint32_t getCompressedSize();

  // Return uncompressed data size
  uint32_t getRealSize();

  // Return uncompressed data
  uint8_t *getData();

  // Return flags
  uint32_t getFlags();

  // Return offset of file data in GRF
  uint32_t getOffset();

  // Save file to given path
  void save(const std::string& filePath);

  // Rename file
  void rename(const std::string& newName);

  // Remove file from GRF
  void remove();

  // Clear memory buffer that used to store data
  // Use this if you want to clear some memory after using getData()
  void free();

  friend class GrfFile;
};

enum GrfFlags {
  GRF_FILE     = 0x01,
  GRF_MIXCRYPT = 0x02,
  GRF_DES      = 0x04,
};

enum SortFlags {
  SORT_SIZE_REAL,
  SORT_SIZE_COMPRESSED,
  SORT_PATH,
};

enum ReturnFlags {
  GRF_OK          = 0,
  GRF_FAIL        = -1,
  GRF_FILE_EXISTS = -2,
  GRF_NOT_VALID   = -2,
};

class GrfFile {
  GrfHeader m_grfHeader;
  std::vector<GrfItem> m_grfItem;
  uint8_t *m_grfTableData;
  uint32_t m_grfTableSize;
  int32_t m_compressionLevel;
  std::fstream m_fileHandle;
  std::fstream m_tempHandle; // For temp file
  std::string m_fileName;
  std::string m_tempName;
public:
  GrfFile();
  GrfFile(const std::string& fileName);
  ~GrfFile();

  // Create a new GRF
  // *return int
  //   0: Success creating file
  //  -1: Can't create file
  //  -2: File already exists
  int create(const std::string& fileName);

  // Save changes that already made
  void save();

  // Open GRF from given pathname
  // *return int
  //   0: Success loading file
  //  -1: Can't open file
  //  -2: Not a valid GRF
  int load(const std::string& fileName);

  // Close currently open GRF
  void close();

  // Get GrfItem struct that hold file data
  inline GrfItem *getItem(uint32_t id);

  // Get GRF Version number
  inline uint32_t getVersion() const;

  // Get number of files in GRF
  inline int getFileCount() const;

  // Set compression level (0 - 9)
  // *level
  //   0: Best Speed
  //   9: Best Compression
  inline void compressionLevel(int level);

  // Insert file from srcPath to destPath in GRF
  // *return int
  //   0: Success inserting file
  //  -1: Can't insert file
  int insert(const std::string& srcPath, const std::string& destPath);

  // Repack GRF
  // You should repack your GRF after already modifying
  // your GRF (remove, insert, etc..) to optimize its size.
  // Save is not needed after repacking
  void repack();

  // Sorting item
  // *flags
  //   SORT_SIZE_REAL: Sort item by its real size
  //   SORT_SIZE_COMPRESSED: Sort item by its compressed size
  //   SORT_PATH: Sort item by its pathname
  void sort(uint32_t flags);

  friend class GrfItem;

private:
  void parseTableData(uint8_t *tableData);
};

inline GrfItem *GrfFile::getItem(uint32_t id) {
  return &m_grfItem.at(id);
}

inline uint32_t GrfFile::getVersion() const {
  return m_grfHeader.version;
}

inline int GrfFile::getFileCount() const {
  return m_grfHeader.number2 - m_grfHeader.number1 - 7;
}

inline void GrfFile::compressionLevel(int level) {
  m_compressionLevel = level > 9 ? 9 : level < 0 ? 0 : level;
}

#endif//_GRFPACK_HPP_
